<?php

declare(strict_types=1);

namespace App\CustHub\Work;

use App\CustHub\Interfaces\OpenWorkApplicationInterface;
use App\CustHub\Utils\OpenWorkAppUtil;
use App\Setting\Service\SettingConfigService;
use EasyWeChat\Kernel\Exceptions\HttpException;
use EasyWeChat\Kernel\Exceptions\InvalidArgumentException;
use EasyWeChat\OpenWork\Application;
use EasyWeChat\OpenWork\SuiteAccessToken;
use EasyWeChat\OpenWork\SuiteTicket;
use GuzzleHttp\Client;
use GuzzleHttp\HandlerStack;
use Hyperf\Context\Context;
use Hyperf\Contract\ConfigInterface;
use Hyperf\Di\Annotation\Inject;
use Hyperf\Guzzle\CoroutineHandler;
use Hyperf\HttpServer\Contract\RequestInterface;
use Mine\Exception\NormalStatusException;
use Psr\Container\ContainerExceptionInterface;
use Psr\Container\NotFoundExceptionInterface;
use Psr\Http\Message\ServerRequestInterface;
use Psr\SimpleCache\CacheInterface;

class OpenWorkApplication implements OpenWorkApplicationInterface
{
    /**
     * @var array wework配置
     */
    protected array $config;

    protected Client $httpClient;

    #[Inject]
    protected SettingConfigService $settingConfigService;

    #[Inject]
    protected CacheInterface $cache;

    public function __construct()
    {
        $this->config = container()->get(ConfigInterface::class)->get('wechat.wework');

        $this->httpClient = new \GuzzleHttp\Client([
            'base_uri' => 'https://qyapi.weixin.qq.com',
            'handler' => HandlerStack::create(new CoroutineHandler()),
            'timeout' => 5,
            'swoole' => [
                'timeout' => 10,
                'socket_buffer_size' => 1024 * 1024 * 2,
            ],
        ]);
    }

    /**
     * @return Application
     * @throws \EasyWeChat\Kernel\Exceptions\InvalidArgumentException
     * @throws \Psr\Container\ContainerExceptionInterface
     * @throws \Psr\Container\NotFoundExceptionInterface
     */
    public function getOpenWork(): Application
    {
        $weWorkConfig = $this->settingConfigService->getConfigByGroupKey('open_work');
        $weWorkConfigGroup = array_column($weWorkConfig, 'value', 'key');
        [$suiteId,$suiteSecret] = OpenWorkAppUtil::getSuiteIdAndSuiteSecret();
        $config = [
            'corp_id' => $weWorkConfigGroup['corp_id'] ?? 'corp_id',
            'provider_secret' => $weWorkConfigGroup['provider_secret'] ?? '',
            'token' => $weWorkConfigGroup['token'] ?? '',
            'aes_key' => $weWorkConfigGroup['aes_key'] ?? '',
            'suite_id' => $suiteId,
            'suite_secret' => $suiteSecret
        ];

        $config = array_merge($config, $this->config);
        $app = new Application($config);

        if (method_exists($app, 'setCache')) {
            $app->setCache($this->cache);
        }
        if (Context::get(ServerRequestInterface::class) && method_exists($app, 'setRequest')) {
            $app->setRequest(container()->get(RequestInterface::class));
        }
        return $app;
    }

    public function getTxlWork(): Application
    {
        $weWorkConfig = $this->settingConfigService->getConfigByGroupKey('open_work');
        $weWorkConfigGroup = array_column($weWorkConfig, 'value', 'key');
        [$suiteId,$suiteSecret] = OpenWorkAppUtil::getSuiteIdAndSuiteSecret();
        $config = [
            'corp_id' => $weWorkConfigGroup['corp_id'] ?? 'corp_id',
            'provider_secret' => $weWorkConfigGroup['provider_secret'] ?? '',
            'token' => 'diNHGf1fXBKWD',
            'aes_key' => 'Vp2HMmpB4IS7xF44DeQm6vhELpOl2N8LimSbetFWCSH',
            'suite_id' => 'ww40ecd746c63396c1',
            'suite_secret' => 'jtDEEepo24BHBv7NfWnfxVAu2VnnvM73HOcVXhrcNwg'
        ];

        $config = array_merge($config, $this->config);
        $app = new Application($config);

        if (method_exists($app, 'setCache')) {
            $app->setCache($this->cache);
        }
        if (Context::get(ServerRequestInterface::class) && method_exists($app, 'setRequest')) {
            $app->setRequest(container()->get(RequestInterface::class));
        }
        return $app;
    }

    public function getAuthQrCode(int $authType,int $mainUid): string
    {
        $app = $this->getOpenWork();
        $providerAccessToken = $app->getProviderAccessToken()->getToken();
        $url = $this->getCustomizedAuthUrl($providerAccessToken,"{$authType}A{$mainUid}",[$app->getAccount()->getSuiteId()]);
        //转二维码
        $qrCode = 'https://router.wikeyun.cn/qr/?text='.$url;
        return $qrCode;
    }


    /**
     * @param string $suiteId
     * @param string $suiteSecret
     * @return SuiteAccessToken
     */
    public function getSuiteAccessToken(string $suiteId, string $suiteSecret): SuiteAccessToken
    {
        // TODO: Implement getSuiteAccessToken() method.
        $verifyTicket = new SuiteTicket(
            suiteId: $suiteId,
            cache: $this->cache,
            key: 'openWorkSuiteTicket:' . $suiteId
        );
        $suiteAccessToken = new SuiteAccessToken(
            suiteId: $suiteId,
            suiteSecret: $suiteSecret,
            suiteTicket: $verifyTicket,
        );
        return $suiteAccessToken;
    }

    public function getTxlSuiteAccessToken(string $suiteId, string $suiteSecret): SuiteAccessToken
    {
        // TODO: Implement getSuiteAccessToken() method.
        $verifyTicket = new SuiteTicket(
            suiteId: $suiteId,
            cache: $this->cache,
            key: 'txlSuiteTicket:' . $suiteId
        );
        $suiteAccessToken = new SuiteAccessToken(
            suiteId: $suiteId,
            suiteSecret: $suiteSecret,
            suiteTicket: $verifyTicket,
        );
        return $suiteAccessToken;
    }

    public function getPermanentCode(string $suiteAccessToken, string $authCode): array
    {
        $response = $this->requestPost('/cgi-bin/service/get_permanent_code?suite_access_token='.$suiteAccessToken, [
            'auth_code' => $authCode
        ],false);
        return $response;
    }

    public function getAuthInfo(string $suiteAccessToken, string $authCorpid, string $permanentCode)
    {
        $response =$this->requestPost('cgi-bin/service/get_auth_info?suite_access_token='.$suiteAccessToken,[
            'auth_corpid' => $authCorpid,
            'permanent_code' => $permanentCode
        ],false);
        return $response;
    }

    public function getAgentInfo(string $accessToken, int $agentId): array
    {
        $response = $this->requestGet('/cgi-bin/agent/get', [
            'access_token' => $accessToken,
            'agentid' => $agentId
        ]);
        return $response;
    }

    /**
     * 获取带参授权链接 十分钟内有效
     * @param string $providerAccessToken
     * @param string $state
     * @param array $templateidList
     * @return string
     * @throws HttpException
     * @throws \Psr\SimpleCache\InvalidArgumentException
     * @throws \Symfony\Contracts\HttpClient\Exception\ClientExceptionInterface
     * @throws \Symfony\Contracts\HttpClient\Exception\DecodingExceptionInterface
     * @throws \Symfony\Contracts\HttpClient\Exception\RedirectionExceptionInterface
     * @throws \Symfony\Contracts\HttpClient\Exception\ServerExceptionInterface
     * @throws \Symfony\Contracts\HttpClient\Exception\TransportExceptionInterface
     */
    public function getCustomizedAuthUrl(string $providerAccessToken,string $state, array $templateidList):string
    {
        $key = 'customized_auth_url:'.md5($providerAccessToken.$state.json_encode($templateidList));
        $customizedAuthUrl = $this->cache->get($key);
        if ((bool) $customizedAuthUrl && \is_string($customizedAuthUrl)) {
            return $customizedAuthUrl;
        }

        $response = $this->requestPost(
            '/cgi-bin/service/get_customized_auth_url?provider_access_token='.$providerAccessToken,[
            'state' => $state,
            'templateid_list' => $templateidList
        ],
        );
        if(empty($response['qrcode_url'])){
            throw new NormalStatusException('Failed to get get_customized_auth_url: '.json_encode($response, JSON_UNESCAPED_UNICODE));
        }

        $qrCodeUrl = $response['qrcode_url'];
        $this->cache->set($key,$qrCodeUrl, abs(intval($response['expires_in']) - 100));
        return $qrCodeUrl;
    }

    public function getAccessToken(string $corpid, string $corpsecret): string
    {
        $key = 'work_agent_access_toke:'.$corpsecret;
        $accessToken = $this->cache->get($key);
        if ((bool) $accessToken && \is_string($accessToken)) {
            return $accessToken;
        }

        $response = $this->requestGet('/cgi-bin/gettoken',[
            'corpid' => $corpid,
            'corpsecret' => $corpsecret
        ]);

        if(empty($response['access_token'])){
            throw new NormalStatusException('Failed to get get_permanent_code: '.json_encode($response, JSON_UNESCAPED_UNICODE));
        }

        $accessToken = $response['access_token'];
        $this->cache->set($key,$accessToken, abs(intval($response['expires_in']) - 100));
        return $accessToken;
    }

    public function corpidToOpencorpid(string $wx_corpid)
    {
        $app = $this->getOpenWork();
        $providerAccessToken = $app->getProviderAccessToken()->getToken();
        $response =$this->requestPost('cgi-bin/service/corpid_to_opencorpid?provider_access_token='.$providerAccessToken,[
            'corpid' => $wx_corpid,
        ],false);
        return $response;
    }


    /**
     * 购买接口许可证
     * @param string $corpid
     * @param string $userId
     * @param int $externalContactCount
     * @param int $months
     * @return string|bool
     * @throws InvalidArgumentException
     * @throws ContainerExceptionInterface
     * @throws NotFoundExceptionInterface
     */
    public function createNewOrder(string $corpid,string $userId,int $externalContactCount,int $months): string|bool
    {
        $app = $this->getOpenWork();
        $providerAccessToken = $app->getProviderAccessToken()->getToken();
        $response =$this->requestPost('cgi-bin/license/create_new_order?provider_access_token='.$providerAccessToken,[
            'corpid' => $corpid,
            'buyer_userid' => $userId,
            'account_count' => [
                'external_contact_count' => $externalContactCount
            ],
            'account_duration' => [
                'months' => $months,
            ]
        ],false);

        if($response['errcode'] !== 0){
            return false;
        }

        return $response['order_id'];
    }

    public function createRenewOrderJob(string $corpid, array $accountList, string $jobid = ''): array
    {
        $app = $this->getOpenWork();
        $providerAccessToken = $app->getProviderAccessToken()->getToken();

        $data = [
            'corpid' => $corpid,
            'account_list' => $accountList
        ];

        !empty($jobid) && $data['jobid'] = $jobid;

        $response =$this->requestPost('cgi-bin/license/create_renew_order_job?provider_access_token='.$providerAccessToken,$data,false);
        return $response;
    }


    public function getOrder(string $orderId):array
    {
        $app = $this->getOpenWork();
        $providerAccessToken = $app->getProviderAccessToken()->getToken();
        $response =$this->requestPost('cgi-bin/license/get_order?provider_access_token='.$providerAccessToken,[
            'order_id' => $orderId,
        ],false);
        if($response['errcode'] !== 0){
            return [];
        }
        return $response['order'];
    }

    public function submitOrderJob(string $jobid, string $buyer_userid, array $account_duration): mixed
    {
        $app = $this->getOpenWork();
        $providerAccessToken = $app->getProviderAccessToken()->getToken();
        $response =$this->requestPost('cgi-bin/license/submit_order_job?provider_access_token='.$providerAccessToken,[
            'jobid' => $jobid,
            'buyer_userid' => $buyer_userid,
            'account_duration' => $account_duration,
        ],false);
        return $response;
    }


    public function submitPayJob(string $payer_userid, string $order_id): mixed
    {
        $app = $this->getOpenWork();
        $providerAccessToken = $app->getProviderAccessToken()->getToken();
        $response =$this->requestPost('cgi-bin/license/submit_pay_job?provider_access_token='.$providerAccessToken,[
            'payer_userid' => $payer_userid,
            'order_id' => $order_id,
        ],false);
        return $response;
    }

    public function getListOrderAccount(string $orderId,string $cursor = '',int $limit = 1000): array
    {
        $app = $this->getOpenWork();
        $providerAccessToken = $app->getProviderAccessToken()->getToken();
        $response =$this->requestPost('cgi-bin/license/list_order_account?provider_access_token='.$providerAccessToken,[
            'order_id' => $orderId,
            'limit' => $limit,
            'cursor' => $cursor,
        ],false);
        return $response;
    }

    public function activeAccount(string $activeCode, string $corpid, string $userid): bool
    {
        $app = $this->getOpenWork();
        $providerAccessToken = $app->getProviderAccessToken()->getToken();
        $this->requestPost('cgi-bin/license/active_account?provider_access_token='.$providerAccessToken,[
            'active_code' => $activeCode,
            'corpid' => $corpid,
            'userid' => $userid
        ]);
        return true;
    }

    public function transferLicense(string $corpid, string $handoverUserid, string $takeoverUserid): bool
    {
        $app = $this->getOpenWork();
        $providerAccessToken = $app->getProviderAccessToken()->getToken();
        $this->requestPost('cgi-bin/license/batch_transfer_license?provider_access_token='.$providerAccessToken,[
            'corpid' => $corpid,
            'transfer_list' => [
                [
                    'handover_userid' => $handoverUserid,
                    'takeover_userid' => $takeoverUserid
                ]
            ]
        ]);
        return true;
    }

    public function getActiveInfoByUser(string $corpId, string $userid)
    {
        $app = $this->getOpenWork();
        $providerAccessToken = $app->getProviderAccessToken()->getToken();
        $response = $this->requestPost('cgi-bin/license/get_active_info_by_user?provider_access_token=' . $providerAccessToken, [
            'corpid' => $corpId,
            'userid' => $userid
        ], false);
        return $response;
    }

    public function paytoolOpenOrder(array $data): array
    {
        $app = $this->getOpenWork();
        $providerAccessToken = $app->getProviderAccessToken()->getToken();
        $response = $this->requestPost('cgi-bin/paytool/open_order?provider_access_token='.$providerAccessToken,$data,false);
        return $response;
    }

    public function paytoolGetOrderList(array $data): array
    {
        $app = $this->getOpenWork();
        $providerAccessToken = $app->getProviderAccessToken()->getToken();
        $response = $this->requestPost('cgi-bin/paytool/get_order_list?provider_access_token='.$providerAccessToken,$data,false);
        return $response;
    }


    private function requestGet(string $url, array $query, bool $exitsErrorCode = true)
    {
        $response = $this->httpClient->request('GET',
            $url,
            [
                'query' => $query,
            ],
        );

        $response = json_decode($response->getBody()->getContents(), true);

        if (($exitsErrorCode && $response['errcode'] != 0) || (!$exitsErrorCode && isset($response['errcode']))) {
            throw new NormalStatusException(json_encode($response, JSON_UNESCAPED_UNICODE));
        }

        return $response;
    }


    private function requestPost(string $url, array $body, bool $exitsErrorCode = true)
    {
        $response = $this->httpClient->request('POST',
            $url,
            [
                'body' => json_encode($body),
            ],
        );

        $response = json_decode($response->getBody()->getContents(), true);
        if (($exitsErrorCode && $response['errcode'] != 0)) {
            throw new NormalStatusException($response['errmsg']);
        }
        return $response;
    }


    public function getApp(mixed $agentId): Application
    {
        // TODO: Implement getApp() method.
    }
}